/** Framework for exercise 4.1 and 4.2
*
* @author
*   Gabriel Zachmann, Sep 2006
*	Daniel Mohr, Nov, 2011
* @bug
*   I'm sure there is still some bugs left ....
**/

#include <QtGui>
#include <QtOpenGL>
#include <QMouseEvent>

#include <math.h>
#include <limits>

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#include "glwidget.h"

const GLdouble GLWidget::FRONT     =   10.0;
const GLdouble GLWidget::BACK      =  400.0;
const GLdouble GLWidget::SV_TRANSZ =  -50;
const GLdouble GLWidget::WV_TRANSZ =  -100;

GLWidget::GLWidget( QWidget *parent )
: QGLWidget(parent)
, m_show(TETRAHEDRON)
, m_xTrans(0.0)
, m_yTrans(0.0)
, m_backgroundColor(0,0,0)
, m_zTrans(SV_TRANSZ)
, m_flakeRecursionDepth(2)
, m_flakeTetraHeight(0.1f)
, m_flakeTetraHeightAttenuation(0)
, m_flakeTetraChildSize(0.5f)
{
	resetRotation();
    setFocusPolicy( Qt::ClickFocus ); // for keyboard-input

	m_regularTetrahedron.reserve(4);
	m_regularTetrahedron.push_back( Vector3(1,1,-1) );
	m_regularTetrahedron.push_back( Vector3(-1,-1,-1) );
	m_regularTetrahedron.push_back( Vector3(-1,1,1) );
	m_regularTetrahedron.push_back( Vector3(1,-1,1) );

	// init random generator
	srand( (unsigned)time( NULL ) );
	slotGenerateScene(10);
}


GLWidget::~GLWidget()
{
    makeCurrent();
}

// set rotation Matrix for trackball to Identity
void GLWidget::resetRotation()
{
	for(int i = 1; i < 4; i++)
	{
		for(int j = 0; j < i; j++)
		{
			m_rotationMatrix[i*4+j] = m_rotationMatrix[j*4+i]= 0.0;
		}
	}
	m_rotationMatrix[0] = m_rotationMatrix[5] = m_rotationMatrix[10] = m_rotationMatrix[15] = 1.0;
}

QSize GLWidget::minimumSizeHint() const
{
    return QSize(800, 800);
}

QSize GLWidget::sizeHint() const
{
    return QSize(800, 800);
}


void GLWidget::initializeGL()
{
	// set color buffer to this color if glClear() is called
    glClearColor( m_backgroundColor[0], m_backgroundColor[1], m_backgroundColor[2], 0.0 );
	// set openGL states
    glEnable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);
	glShadeModel(GL_FLAT); // we use flat shading

	glEnable(GL_LIGHTING);
	GLfloat light_ambient[] = { 0.1f, 0.1f, 0.1f, 1.0f };
	GLfloat light_diffuse[] = { 0.45f, 0.45f, 0.45f, 1.0f };
	GLfloat light_specular[] = { 0.2f, 0.2f, 0.2f, 1.0f };

	glLightModelfv( GL_LIGHT_MODEL_AMBIENT, light_ambient );

	glEnable(GL_LIGHT0);
	glLightfv( GL_LIGHT0, GL_DIFFUSE , light_diffuse );
	glLightfv( GL_LIGHT0, GL_SPECULAR, light_specular );
	GLfloat light0_position[] = { 1.0f, 1.0f, 0.5f, 0.0f };
	glLightfv( GL_LIGHT0, GL_POSITION, light0_position);

	glEnable(GL_LIGHT1);
	glLightfv( GL_LIGHT1, GL_DIFFUSE , light_diffuse );
	glLightfv( GL_LIGHT1, GL_SPECULAR, light_specular );
	GLfloat light1_position[] = { -1.0f,-1.0f, 0.5f, 0.0f };
	glLightfv( GL_LIGHT1, GL_POSITION, light1_position);

	glEnable(GL_LIGHT2);
	glLightfv( GL_LIGHT2, GL_DIFFUSE , light_diffuse );
	glLightfv( GL_LIGHT2, GL_SPECULAR, light_specular );
	GLfloat light2_position[] = { 0.0f,0.0f, 1.0f, 0.0f };
	glLightfv( GL_LIGHT2, GL_POSITION, light2_position);

	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );
}


void GLWidget::paintGL()
{
	// clear framebuffer
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	glMatrixMode(GL_MODELVIEW);
	// push current matrix to stack
	glPushMatrix();

	// manage simple navigation ( rotation and translation )
	glLoadIdentity();
	glTranslatef(m_xTrans, m_yTrans, m_zTrans);
	glMultMatrixf(m_rotationMatrix); //

	switch ( m_show ) // for exercise one
	{
	case TETRAHEDRON:
		glColor3f(1.0,0,0);
		drawTetrahedron( m_regularTetrahedron[0], m_regularTetrahedron[1], m_regularTetrahedron[2], m_regularTetrahedron[3] );
		break;
	case TETRA_FLAKE:
		drawTetraFlake( m_regularTetrahedron[0], m_regularTetrahedron[1], m_regularTetrahedron[2], m_regularTetrahedron[3], 
			m_flakeRecursionDepth, m_flakeTetraChildSize, m_flakeTetraHeight, m_flakeTetraHeightAttenuation );
		break;
	case SZENE:
		drawSzene();
		break;
	default: break;
	}

	// restore matrix from stack
	glPopMatrix();
}


void GLWidget::resizeGL( int w, int h )		// = width & height
{
    int side = qMin(w, h);
    glViewport((w - side) / 2, (h - side) / 2, side, side);

	updateProjectionMatrix(FRONT, BACK);
}

void GLWidget::updateProjectionMatrix(GLdouble front, GLdouble back)
{
	glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
	glFrustum(-1.0, 1.0, -1.0, 1.0, front, back); // width,height = 2, depth = back-front
    glMatrixMode(GL_MODELVIEW);
}

void GLWidget::mousePressEvent( QMouseEvent * e /*the event*/ )
{
    m_lastPos = e->pos();
}


void GLWidget::keyPressEvent ( QKeyEvent * e )
{
    if ( !isValid() )
        return;

    switch ( e->key() )
    {
        case Qt::Key_B: // switch background color if key "B" is pressed
			slotSwitchBackgroundColor();
            break;
        default:
            return;
    }       
}

void GLWidget::slotSwitchBackgroundColor()
{
	if ( m_backgroundColor[0] > 0.5f )
	{
		m_backgroundColor.set(0.0f,0.0f,0.0f);
	} 
	else
	{
		m_backgroundColor.set(1.0f,1.0f,1.0f);
	}
	glClearColor( m_backgroundColor[0], m_backgroundColor[1], m_backgroundColor[2], 0.0 );
	updateGL();	// openGL window has to be redrawn
}

void GLWidget::mouseMoveEvent( QMouseEvent * e /*the event*/ )
{
    int dx = e->x() - m_lastPos.x();
    int dy = e->y() - m_lastPos.y();

	bool ctrl_key = e->modifiers() & Qt::MetaModifier;		// only needed for Mac OS X, but doesn't hurt on other OSes

	if ( (e->buttons() & Qt::RightButton) || ctrl_key ) // translation along z-Axis
	{
		m_zTrans += 0.2 * dy;
	}
	else if ( e->buttons() & Qt::MidButton ) // translation in xy-Plane
	{
		m_xTrans += 0.01 * dx;
		m_yTrans -= 0.01 * dy;
	}
	else if (e->buttons() & Qt::LeftButton)	// rotation
	{
		// openGL multiplies new transformations on the right
		// we want to apply rotation on the left
		glPushMatrix(); // push current openGL transform matrix to stack
		glLoadIdentity();
		glRotatef(0.5*dx,0.0, 1.0, 0.0);
		glRotatef(0.5*dy,1.0, 0.0, 0.0);
		glMultMatrixf(m_rotationMatrix);
		glGetFloatv(GL_MODELVIEW_MATRIX,m_rotationMatrix);
		glPopMatrix();
    }
    m_lastPos = e->pos();
	e->accept();
	updateGL();
}

void GLWidget::slotShowTetrahedron(bool checked)
{
	if ( checked )
	{
		m_show = TETRAHEDRON;
		updateProjectionMatrix(FRONT, BACK);
		m_xTrans = m_yTrans = 0;
		m_zTrans = SV_TRANSZ;
		resetRotation();
		updateGL();
	}
}

void GLWidget::slotShowTetraFlake(bool checked)
{
	if ( checked )
	{
		m_show = TETRA_FLAKE;
		updateProjectionMatrix(FRONT, BACK);
		m_xTrans = m_yTrans = 0;
		m_zTrans = SV_TRANSZ;
		resetRotation();
		updateGL();
	}
}

void GLWidget::slotShowSzene(bool checked)
{
	if ( checked )
	{
		m_show = SZENE;
		updateProjectionMatrix(FRONT, BACK);
		m_xTrans = m_yTrans = 0;
		m_zTrans = WV_TRANSZ;
		resetRotation();
		updateGL();
	}
}

void GLWidget::setFlakeRecursionDepth(int depth)
{
	m_flakeRecursionDepth = depth;
	updateGL();
}

void GLWidget::setFlakeTetraHeight(float height)
{
	m_flakeTetraHeight = height;
	updateGL();
}

void GLWidget::setFlakeTetraHeightAttenuation(float att)
{
	m_flakeTetraHeightAttenuation = att;
	updateGL();
}

void GLWidget::setFlakeTetraChildSize(float size)
{
	m_flakeTetraChildSize = size;
	updateGL();
}


void GLWidget::slotGenerateScene( int numberOfObjects )
{
	// a plane "corner"
	m_plane_point.set(-4,-4, 0);
	// direction vector 1
	m_plane_vectors[0].set( 1, 0, 0);
	// direction vector 2
	m_plane_vectors[1].set( 0, 1, 0 );

	// corner of bounding box for tetraflake centers
	static const double range_min[3] = { -4.0, -4.0, -4.0 };
	static const double range_max[3] = { 4.0, 4.0, 4.0 };

	// generate random positions for all tetraFlakes
	m_tetraFlakeCenters.resize(numberOfObjects);
	for ( int i = 0; i < numberOfObjects; i++)
	{
		for ( int d = 0; d < 3; d++)
		{
			m_tetraFlakeCenters[i][d] = static_cast<float>( rand() / (RAND_MAX + 1.0) * (range_max[d] - range_min[d]) + range_min[d] );
		}
	}
}

// ** TODO **
// draw a tetrahedron with the 4 vertices p0,p1,p2,p3
// recDepth is the recursion depth
// childSize \in [0,1] controls the size of base triangle of the child tetrahedron
// height interpolates between height 0 and length( (p1-p0)x(p2-p0) ) -> actual height of a tetrahedron side
// heightAttenuation for each recursion level, height <- height * (1-heightAttenuation)
void GLWidget::drawTetrahedron(const Vector3 &p0, const Vector3 &p1, const Vector3 &p2, const Vector3 &p3)
{
    glBegin(GL_TRIANGLES);
    glColor3f(1.0,0.0,0.0);
    glVertex3f(p0.getX(),p0.getY(),p0.getZ());
    glVertex3f(p1.getX(),p1.getY(),p1.getZ());
    glVertex3f(p2.getX(),p2.getY(),p2.getZ());
    glColor3f(0.0,1.0,0.0);
    glVertex3f(p1.getX(),p1.getY(),p1.getZ());
    glVertex3f(p2.getX(),p2.getY(),p2.getZ());
    glVertex3f(p3.getX(),p3.getY(),p3.getZ());
    glColor3f(0.0,0.0,1.0);
    glVertex3f(p2.getX(),p2.getY(),p2.getZ());
    glVertex3f(p3.getX(),p3.getY(),p3.getZ());
    glVertex3f(p0.getX(),p0.getY(),p0.getZ());
    glColor3f(1.0,1.0,0.0);
    glVertex3f(p0.getX(),p0.getY(),p0.getZ());
    glVertex3f(p1.getX(),p1.getY(),p1.getZ());
    glVertex3f(p3.getX(),p3.getY(),p3.getZ());
    glEnd();
}

void GLWidget::drawTetraFlake(const Vector3 &p0, const Vector3 &p1, const Vector3 &p2, const Vector3 &p3, int recDepth, float childSize, float height, float heightAttenuation)
{

}

void GLWidget::drawSzene()
{
	glDisable(GL_LIGHTING);
	glColor3f( 1, 1, 1 );
	drawPlane(m_plane_point, m_plane_vectors[0], m_plane_vectors[1], 8);

	glEnable(GL_LIGHTING);
    glColor3f(0,1,0);
	for ( unsigned int i = 0; i < m_tetraFlakeCenters.size(); i++)
	{
		glPushMatrix();

		drawTetraFlake( m_tetraFlakeCenters[i] + m_regularTetrahedron[0],
						m_tetraFlakeCenters[i] + m_regularTetrahedron[1], 
						m_tetraFlakeCenters[i] + m_regularTetrahedron[2], 
						m_tetraFlakeCenters[i] + m_regularTetrahedron[3],
						m_flakeRecursionDepth, m_flakeTetraChildSize, m_flakeTetraHeight, m_flakeTetraHeightAttenuation );
		glPopMatrix();
	}
}

/*!
 * p0 is a point on the plane
 * d1,d2 two vectors defining the plane direction
 * numCells defines the number of cells (rectangles) drawn
 *   the size of each cell is determined by length(d1) and length(d2)
 * precondition: d1,d2 are NOT linearly dependent
 */
void GLWidget::drawPlane( const Vector3 &p0, const Vector3 &d1, const Vector3 &d2, int numCells )
{
	const Vector3 &dx = d1;
	// ensure, that dx and dy are orthogonal
	const Vector3 dy = cross ( cross(d1,d2), d1) ; 

	glBegin(GL_LINES);
	// lines along dx
	{
		Vector3 sx = p0;
		Vector3 ex = p0 + dy * numCells;
		for ( int i = 0; i <= numCells; i++)
		{
			glVertex3f( sx[0], sx[1], sx[2] );
			glVertex3f( ex[0], ex[1], ex[2] );

			sx += dx;
			ex += dx;
		}
	}
	// lines along dy
	{
		Vector3 sy = p0;
		Vector3 ey = p0 + dx * numCells;
		for ( int i = 0; i <= numCells; i++)
		{
			glVertex3f( sy[0], sy[1], sy[2] );
			glVertex3f( ey[0], ey[1], ey[2] );

			sy += dy;
			ey += dy;
		}
	}

	glEnd();

}

